package com.cctv.haierstore.product.annotation.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.cctv.haierstore.product.annotation.MallCache;
import com.cctv.haierstore.product.vo.Catalog2Vo;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author huayi
 * @date 2023年12月17日 20:07
 */

@Aspect
@Component
public class MallCacheAspect {
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    RedissonClient redissonClient;

    //环绕通知: 对使用自定义注解GmallCache的方法进行增强
    //@annotation 对指定注解进行切面通知
    @Around("@annotation(com.cctv.haierstore.product.annotation.MallCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {//切入点： 可以获取到切入点方法对象 和参数等信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取切入点方法信息
        Method method = signature.getMethod();//获取目标方法的对象
        Class returnType = signature.getReturnType();
        //1.获取目标方法的返回值类型
        System.out.println("目标方法的返回值类型: "+returnType.getName());

        //2、获取目标方法的参数列表
//        Object[] args = joinPoint.getArgs();
//        System.out.println("目标方法的参数: "+args[0].toString());

        //3、获取目标方法上的注解对象：获取注解对象中的 缓存key  lockkey  过期时间...
        MallCache gmallCache = method.getAnnotation(MallCache.class);
        String key = gmallCache.key();
        String lockName = gmallCache.lockName();
        int timeout = gmallCache.timeout();
        int random = gmallCache.random();
        System.out.println("目标方法上的注解: "+"key="+key+",lockName="+lockName+",timeout="+timeout+",random="+random);


        //4，判断是否存在缓存，有缓存则直接返回
        String cacheKey = key;
        cacheKey = key;
        lockName = lockName;

        //使用布隆过滤器去决定是否查询缓存。。 todo

        // redisTemplate配置过键和值的序列化器，所以返回的Object真实类型就是原数据自己的类型
        String catelogJSON = redisTemplate.opsForValue().get(cacheKey);
        if(StringUtils.isNotEmpty(catelogJSON)){
            return JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catalog2Vo>>>() {
            });
        }

        //5、缓存不存在，执行目标方法:查询数据库中的二级分类和子集合数据
        //分布式锁：解决雪崩
        RLock lock = redissonClient.getLock(lockName);
        lock.lock();
        //再次判断是否有缓存
        catelogJSON = redisTemplate.opsForValue().get(cacheKey);


        try{
            if(StringUtils.isNotEmpty(catelogJSON)){
                return JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catalog2Vo>>>() {
                    });
            }
            Map<String, List<Catalog2Vo>> result = (Map<String, List<Catalog2Vo>>) joinPoint.proceed();
            long cacheTime = timeout+new Random().nextInt(random);
            if(result == null  || (result instanceof List && CollectionUtils.isEmpty((List)result))){
                //空值也存入到缓存中  时间稍短
                cacheTime = random;
            }
            catelogJSON = JSON.toJSONString(result);
            //存入缓存
            redisTemplate.opsForValue().set(cacheKey , catelogJSON , cacheTime, TimeUnit.SECONDS);
            return result;
        }finally {
            lock.unlock();
        }
    }
}
